package com.spzx.product.service.impl;

import com.spzx.product.service.TestService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class TestServiceImpl implements TestService {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Override
    public void testLock() throws InterruptedException {
        //一、获取分布式锁
        //1.初版分布式锁：采用setnx命令 del释放锁实现   问题：在加锁成功后，业务执行过程中服务器宕机，无法释放锁，造成死锁
        String lock = "lock";
        //String lockValue = "lock";

        //版本4：生成锁的值（UUID）作为 释放锁判断条件
        String lockValue = UUID.randomUUID().toString();
        //Boolean flag = redisTemplate.opsForValue().setIfAbsent(lock, lockValue);
        //版本二：加锁成功后，利用expire命令设置锁过期时间 expire lock 5 秒  问题：加锁跟设置不同两个命令，不具备原子性，加锁后服务器立马宕机，有没有设置锁过期时间，造成死锁
        //redisTemplate.expire(lock, 5, TimeUnit.SECONDS);

        //版本三：利用set命令配合 参数 EX NX进行加锁 set lock lockValue EX 5 NX
        Boolean flag = redisTemplate.opsForValue().setIfAbsent(lock, lockValue, 5, TimeUnit.SECONDS);

        if (flag) {
            try {

                //二、获取锁成功，执行业务，并释放锁
                log.info("线程{}加锁成功，执行业务.", Thread.currentThread().getName());
                //1.查询Redis中的Key为num的值  测试前手动创建numkey将值设置为0
                String num = redisTemplate.opsForValue().get("num");
                if (num == null) {
                    return;
                }
                //2.查询到num值 对num值进行+1操作
                int i = Integer.parseInt(num);
                redisTemplate.opsForValue().set("num", String.valueOf(++i));
            } catch (NumberFormatException e) {
                throw new RuntimeException(e);
            } finally {
                //版本4：释放锁前，判断当前线程是否是加锁的线程
                //if (lockValue.equals(redisTemplate.opsForValue().get(lock))) {
                //    // 从当前线程java虚拟机栈（栈帧中本地变量变量表）获取当前线程获取锁前删除锁值 跟 Redis中存在锁值比较
                //    redisTemplate.delete(lock);
                //}

                //版本5：释放锁判断跟删除采用lua脚本实现
                //创建脚本对象 封装脚本内容
                String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" + "then\n" + "    return redis.call(\"del\",KEYS[1])\n" + "else\n" + "    return 0\n" + "end";
                DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<>(script, Boolean.class);
                redisTemplate.execute(redisScript, Arrays.asList(lock), lockValue);
            }
        } else {
            //三、获取锁失败，自旋  业务要求：每隔线程都得完成+1
            log.error("线程{}加锁失败，再次重试.", Thread.currentThread().getName());
            Thread.sleep(500);
            this.testLock();
        }
    }
}
